//go:build linux

package gohttp

import (
	"bufio"
	"bytes"
	"errors"
	"log"
	"net"
	"net/http"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

func NewGoHttp() *GoHttp {
	return &GoHttp{}
}

type GoHttp struct {
	size     int
	events   []syscall.EpollEvent
	server   *http.Server
	conn_map map[int32]*conn
	mux      sync.Mutex
}

func (c *GoHttp) GetConn(fd int32) *conn {
	c.mux.Lock()
	defer c.mux.Unlock()
	if conn, ok := c.conn_map[fd]; ok {
		return conn
	}
	conn := NewConn(fd)
	c.conn_map[fd] = conn
	return conn
}

func (c *GoHttp) DelConn(fd int32) error {
	c.mux.Lock()
	defer c.mux.Unlock()

	if conn, ok := c.conn_map[fd]; ok {
		conn.Release()
		delete(c.conn_map, fd)
	}

	return nil
}

func (c *GoHttp) CheckConn(timeout int64) {
	c.mux.Lock()
	defer c.mux.Unlock()

	for fd, conn := range c.conn_map {
		if conn.IsTimeout(timeout) {
			conn.Release()
			delete(c.conn_map, fd)
		}
	}
}

func (c *GoHttp) Start(server *http.Server) error {
	//使用系统调用,打开一个socket
	fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, syscall.IPPROTO_TCP)
	if err != nil {
		return err
	}

	c.server = server

	//ip地址转换
	var addr [4]byte
	temp := strings.Split(c.server.Addr, ":")
	if len(temp) != 2 {
		return errors.New("addr error")
	}
	copy(addr[:], net.ParseIP(temp[0]).To4())
	port, err := strconv.Atoi(temp[1])
	if err != nil {
		return err
	}
	if err = syscall.Bind(fd, &syscall.SockaddrInet4{Port: port, Addr: addr}); err != nil {
		return err
	}

	c.size = 1024

	//开启监听
	if err = syscall.Listen(fd, c.size); err != nil {
		return err
	}

	// 开启epoll
	c.events = make([]syscall.EpollEvent, c.size)
	epollFd, err := syscall.EpollCreate1(syscall.EPOLL_CLOEXEC)
	if err != nil {
		syscall.Close(epollFd)
		return err
	}

	c.conn_map = make(map[int32]*conn)

	go func(timeout int64) {
		for {
			c.CheckConn(timeout)
			time.Sleep(time.Duration(timeout) * time.Second)
		}
	}(3)

	go func() {
		var msec = -1
		for {
			// 有读写事件到来就会得到通知
			nready, err := syscall.EpollWait(epollFd, c.events, msec)
			if nready <= 0 {
				msec = -1
				runtime.Gosched() // 让出CPU时间给其他goroutine
				continue
			}
			msec = 0
			if err != nil {
				if err == syscall.EINTR {
					continue
				}
				log.Printf("listenner wait err : %v", err)
				return
			}

			buffio := bufio.NewReaderSize(bytes.NewReader([]byte{}), 1600) //单个报文最大长度为1500字节，这里1600字节足够了

			for i := 0; i < nready; i++ {
				if c.events[i].Events&syscall.EPOLLIN|syscall.EPOLLPRI != 0 {
					fd := c.events[i].Fd

					buffio.Reset(c.GetConn(fd))

					r, err := c.readRequest(buffio)
					if r == nil || err != nil { // 收到数据长度为0的时候, 是无法解析出r的, 此时关闭连接，CPU占用会下降非常多
						//log.Println(err)
						c.DelConn(fd)

					} else {
						ev := syscall.EpollEvent{
							Events: syscall.EPOLLOUT,
							Fd:     fd,
						}
						if err := syscall.EpollCtl(epollFd, syscall.EPOLL_CTL_MOD, int(fd), &ev); err != nil {
							log.Println("get data err")
							continue
						}

						c.server.Handler.ServeHTTP(NewResponse(fd), r)
					}
					continue
				}

				if c.events[i].Events&syscall.EPOLLHUP|syscall.EPOLLERR != 0 {
					c.DelConn(c.events[i].Fd)
					continue
				}
			}
		}
	}()

	for {
		// 获取连接
		conn, _, err := syscall.Accept(fd)
		if err != nil {
			return err
		}
		if err = syscall.SetNonblock(conn, true); err != nil {
			return err
		}
		ev := syscall.EpollEvent{
			Fd:     int32(conn),
			Events: syscall.EPOLLPRI | syscall.EPOLLIN | syscall.EPOLLHUP | syscall.EPOLLERR,
		}
		// 把链接注册到epoll中
		if err = syscall.EpollCtl(epollFd, syscall.EPOLL_CTL_ADD, conn, &ev); err != nil {
			return err
		}
	}
}
